package com.xinqi.modules.user.auth.service;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import com.aliyun.oss.ServiceException;
import com.xinqi.common.core.constant.CacheConstants;
import com.xinqi.common.core.constant.ChatRemindConstants;
import com.xinqi.common.core.constant.Constants;
import com.xinqi.common.core.constant.UserConstants;
import com.xinqi.common.core.enums.*;
import com.xinqi.common.core.exception.user.UserException;
import com.xinqi.common.core.utils.*;
import com.xinqi.common.log.syslog.event.LogininforEvent;
import com.xinqi.common.redis.utils.RedisUtils;
import com.xinqi.common.satoken.utils.LoginHelper;
import com.xinqi.modules.chat.chat.official.client.ChatRemindContentClient;
import com.xinqi.modules.chat.chat.official.dto.req.ChatRemindContentCreateDTO;
import com.xinqi.modules.chat.chat.rongyun.RemoteRongyunClient;
import com.xinqi.modules.course.course.client.RemoteCourseClient;
import com.xinqi.modules.user.auth.form.*;
import com.xinqi.modules.user.auth.properties.UserPasswordProperties;
import com.xinqi.modules.user.auth.util.AesEncryptUtils;
import com.xinqi.modules.user.socket.dto.rsp.MessageInfo;
import com.xinqi.modules.user.third.dto.req.UserThirdCheckDTO;
import com.xinqi.modules.user.user.dto.req.PasswordResetDTO;
import com.xinqi.modules.user.user.dto.req.RegisterLastStepDTO;
import com.xinqi.modules.user.user.dto.req.UserRegisterDTO;
import com.xinqi.modules.user.user.dto.req.UsersUpdateDTO;
import com.xinqi.modules.user.user.dto.rsp.UserLoginResultDTO;
import com.xinqi.modules.user.user.dto.rsp.UserResultDTO;
import com.xinqi.modules.user.users.third.domain.UserLoginLogEntity;
import com.xinqi.modules.user.user.ueuse.dto.req.UserUseCreateDTO;
import com.xinqi.common.base.error.CommonErrors;
import com.xinqi.common.base.response.R;
import com.xinqi.common.base.user.Account;
import com.xinqi.common.base.utils.json.JSON;
import com.xinqi.common.base.utils.spring.Spring;
import com.xinqi.modules.user.users.ueuse.service.UserUseLogService;
import com.xinqi.modules.user.users.user.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

/**
 * APP登录校验方法
 *
 * @author dgyu
 */
@Log4j2
@Service
@RequiredArgsConstructor
public class SysAppLoginService {

    private final UserService userService;

    private final UserPasswordProperties userPasswordProperties;

    private final ChatRemindContentClient remindContentClient;

    private final RemoteCourseClient remoteCourseClient;


    private final RemoteRongyunClient rongyunClient;

    private final UserUseLogService userUseLogService;

    /**
     * 账号密码登录
     * @author dzy
     */
    public R<Map<String, Object>> login(LoginBody form, DeviceType deviceType, boolean ase) {
        // 登录去掉密码格式校验，直接判断登录密码正确
        if (ase) {
            try {
                form.setPassword(AesEncryptUtils.decryptIV(form.getPassword(), AesEncryptUtils.SIGNCODE));
            } catch (Exception e) {
                return R.fail("密码错误");
            }
        }
        checkXqULogin(form.getUsername());
        UserLoginResultDTO userInfo = userService.selectLoginMsgByMobile(form.getUsername());
        CommonErrors.BAD_REQUEST.check(ObjectUtil.isNotNull(userInfo), "用户名或密码错误");
        CommonErrors.BAD_REQUEST.check(StringUtils.isNotEmpty(userInfo.getRongyunToken()), "请重新登录");

        // 校验密码 (更换加密校验判断是否设置加密盐 没有使用旧的校验方式生成新的校验)
        if (Objects.nonNull(userInfo.getSalt()) && !Objects.equals("", userInfo.getSalt())) {
            checkXqLogin(form.getUsername(), () -> !Md5HashUtil.checkPwd(userInfo.getSalt(), form.getPassword(), userInfo.getPassword()));
        } else {
            boolean checkPwd = BCrypt.checkpw(form.getPassword(), userInfo.getPassword());
            checkXqLogin(form.getUsername(), () -> !checkPwd);
            if (checkPwd) {
                this.updatePwd(userInfo, form.getPassword());
            }
        }

        // 获取登录token
        LoginHelper.loginXqByDevice(userInfo, deviceType);

        // 接口返回信息
        Map<String, Object> rspMap = new HashMap<>();
        rspMap.put("access_token", StpUtil.getTokenValue());
        rspMap.put("user", userInfo);
        if (StringUtils.isEmpty(userInfo.getSchoolName())) {
            return R.ok(201, "需要补充用户信息", rspMap);
        }

        // 记录使用登录
        this.loginUserLog(UserUseTypeEnum.LOGIN, StpUtil.getLoginDevice().toLowerCase());

        return R.ok("登录成功", rspMap);
    }

    /**
     * 更新新加密密码
     * @author dzy
     */
    private void updatePwd (UserLoginResultDTO userInfo, String passWord) {
        UsersUpdateDTO update = new UsersUpdateDTO();
        update.setId(userInfo.getId());
        String salt = Md5HashUtil.randomSalt();
        update.setSalt(salt);
        userInfo.setSalt(salt);
        String md5Pwd = Md5HashUtil.encryptPwd(update.getSalt(), passWord);
        userInfo.setPassword(md5Pwd);
        update.setPassword(md5Pwd);
        userService.updateById(update);
    }

    /**
     * 校验用户是否已注册
     * @author dzy
     */
    public R<Void> checkUserRegister(String mobile) {
        // 通过手机号查找用户
        UserResultDTO result = userService.getXqUserInfoByPhone(mobile);
        if (result != null) {
            return R.ok(MessageUtils.message("user.has.retist.valid.yes"));
        } else {
            return R.fail(RegisterReturnEnum.USER_NOT_REGISTER.getCode(), RegisterReturnEnum.USER_NOT_REGISTER.getDescription());
        }
    }

    /**
     * 手机号验证码登录
     * @author dzy
     */
    public R<Map<String, Object>> smsLogin(SmsLoginBody smsLoginBody, DeviceType deviceType) {

        Map<String, Object> map = new HashMap<>();
        CommonErrors.BAD_REQUEST.check(Objects.nonNull(smsLoginBody.getMobile()), MessageUtils.message("user.phonenumber.not.blank"));
        CommonErrors.BAD_REQUEST.check(Objects.nonNull(smsLoginBody.getSmsCode()), MessageUtils.message("user.jcaptcha.not.blank"));
        CommonErrors.BAD_REQUEST.check(validateSmsCode(smsLoginBody.getMobile(), smsLoginBody.getSmsCode(), false), MessageUtils.message("user.jcaptcha.error"));

        //校验短信验证码
        checkXqSmsLogin(smsLoginBody.getMobile(), () -> !validateSmsCode(smsLoginBody.getMobile(), smsLoginBody.getSmsCode(), false));

        // 通过手机号查找用户
        UserResultDTO userInfo = userService.getXqUserInfoByPhone(smsLoginBody.getMobile());
        //用户不存在则注册
        if (userInfo == null) {
            return R.ok(RegisterReturnEnum.SUPPLY_INFO.getCode(), RegisterReturnEnum.USER_NOT_REGISTER.getDescription(), map);
        }
        // 二次校验清空验证码
        validateSmsCode(smsLoginBody.getMobile(), smsLoginBody.getSmsCode(), true);

        if (StringUtils.isEmpty(userInfo.getRongyunToken())) {
            throw new ServiceException("请重新登录");
        }

        // 生成token
        LoginHelper.loginXqByDevice(userInfo, deviceType);
        String token = StpUtil.getTokenValue();
        map.put("access_token", token);
        map.put("user", userInfo);

        if (StringUtils.isEmpty(userInfo.getSchoolName())) {
            return R.ok(RegisterReturnEnum.SUPPLY_INFO.getCode(), RegisterReturnEnum.SUPPLY_INFO.getDescription(), map);
        }

        // 记录使用登录
        this.loginUserLog(UserUseTypeEnum.LOGIN, StpUtil.getLoginDevice().toLowerCase());

        return R.ok("登录成功", map);
    }

    /**
     * 用户注册校验手机号验证码第一步
     * @author dzy
     */
    public R<Map<String, Object>> checkSmsCode(SmsLoginBody smsLoginBody, DeviceType deviceType) {

        CommonErrors.BAD_REQUEST.check(Objects.nonNull(smsLoginBody.getMobile()), MessageUtils.message("user.phonenumber.not.blank"));
        CommonErrors.BAD_REQUEST.check(Objects.nonNull(smsLoginBody.getSmsCode()), MessageUtils.message("user.jcaptcha.not.blank"));
        CommonErrors.BAD_REQUEST.check(validateSmsCode(smsLoginBody.getMobile(), smsLoginBody.getSmsCode(), false), MessageUtils.message("user.jcaptcha.error"));

        //校验短信验证码
        checkXqSmsLogin(smsLoginBody.getMobile(), () -> !validateSmsCode(smsLoginBody.getMobile(), smsLoginBody.getSmsCode(), false));

        // 通过手机号查找用户
        UserResultDTO userInfo = userService.getXqUserInfoByPhone(smsLoginBody.getMobile());
        //用户不存在则注册
        if (Objects.isNull(userInfo)) {
            return R.ok(RegisterReturnEnum.SUPPLY_INFO.getCode(), RegisterReturnEnum.USER_NOT_REGISTER.getDescription(), new HashMap<>());
        }

        return R.ok("校验成功");
    }

    /**
     * 第三方账号登录 已启用
     * @author dzy
     */
    @Deprecated
    public R<Map<String, Object>> thirdSmsLogin(SmsLoginBody smsLoginBody) {

        Map<String, Object> map = new HashMap<>();
        CommonErrors.BAD_REQUEST.check(Objects.nonNull(smsLoginBody.getMobile()), MessageUtils.message("user.phonenumber.not.blank"));
        CommonErrors.BAD_REQUEST.check(Objects.nonNull(smsLoginBody.getSmsCode()), MessageUtils.message("user.jcaptcha.not.blank"));
        CommonErrors.BAD_REQUEST.check(validateSmsCode(smsLoginBody.getMobile(), smsLoginBody.getSmsCode(), false), MessageUtils.message("user.jcaptcha.error"));

        // 通过手机号查找用户
        UserResultDTO userInfo = userService.getXqUserInfoByPhone(smsLoginBody.getMobile());
        //用户不存在则注册
        if (userInfo == null) {
            return R.ok(RegisterReturnEnum.SUPPLY_INFO.getCode(), RegisterReturnEnum.SUPPLY_INFO.getDescription(), map);
        }
        //校验短信验证码
        checkXqSmsLogin(smsLoginBody.getMobile(), () -> !validateSmsCode(smsLoginBody.getMobile(), smsLoginBody.getSmsCode(), true));
        // 生成token
        LoginHelper.loginXqByDevice(userInfo, DeviceType.getDeviceType(smsLoginBody.getDeviceType()));
        String token = StpUtil.getTokenValue();
        map.put("access_token", token);
        map.put("user", userInfo);

        //用户绑定第三方账户
        bindThirdSmsLogin(smsLoginBody, userInfo);

        UserLoginLogEntity log = new UserLoginLogEntity();
        log.setLastLogin(DateUtils.getNowDateTime());
        log.setUserId(userInfo.getId());
        log.setType(LoginAuthType.MOBILE.getAuthType());
        log.setPsystem(smsLoginBody.getDeviceType());
        userService.generateLog(log);
        if (StringUtils.isEmpty(userInfo.getSchoolName())) {
            return R.ok(RegisterReturnEnum.SUPPLY_INFO.getCode(), RegisterReturnEnum.SUPPLY_INFO.getDescription(), map);
        }
        return R.ok(map);
    }

    /**
     * 绑定第三方账号 已启用
     * @author dzy
     */
    @Deprecated
    private void bindThirdSmsLogin(SmsLoginBody smsLoginBody, UserResultDTO user) {
        // 绑定手机号
        UserThirdCheckDTO checkDto = new UserThirdCheckDTO();
        checkDto.setUserId(user.getId());
        boolean updateFlag = false;
        if (Objects.nonNull(smsLoginBody.getWxopenId())) {
            checkDto.setType(LoginTypeEnum.WX_LOGIN.getCode().toString());
            checkDto.setIdentification(smsLoginBody.getWxopenId());
            updateFlag = true;
        }
        if (Objects.nonNull(smsLoginBody.getQqopenId())) {
            checkDto.setType(LoginTypeEnum.QQ_LOGIN.getCode().toString());
            checkDto.setIdentification(smsLoginBody.getQqopenId());
            updateFlag = true;
        }
        if (Objects.nonNull(smsLoginBody.getAppleUid())) {
            checkDto.setType(LoginTypeEnum.APPLEID_LOGIN.getCode().toString());
            checkDto.setIdentification(smsLoginBody.getAppleUid());
            updateFlag = true;
        }
        if (updateFlag) {
            userService.thirdBindUser(checkDto);
        }
    }

    /**
     * 第三方账号注册 已启用
     * @author dzy
     */
    @Deprecated
    private void bindThirdRegister(UserRegisterDTO registerDto, UserResultDTO user) {
        UserThirdCheckDTO checkDto = new UserThirdCheckDTO();
        checkDto.setUserId(user.getId());
        boolean updateFlag = false;
        if (Objects.nonNull(registerDto.getWxopenId())) {
            checkDto.setType(LoginTypeEnum.WX_LOGIN.getCode().toString());
            checkDto.setIdentification(registerDto.getWxopenId());
            updateFlag = true;
        }
        if (Objects.nonNull(registerDto.getQqopenId())) {
            checkDto.setType(LoginTypeEnum.QQ_LOGIN.getCode().toString());
            checkDto.setIdentification(registerDto.getQqopenId());
            updateFlag = true;
        }
        if (Objects.nonNull(registerDto.getAppleUid())) {
            checkDto.setType(LoginTypeEnum.APPLEID_LOGIN.getCode().toString());
            checkDto.setIdentification(registerDto.getAppleUid());
            updateFlag = true;
        }
        if (updateFlag) {
            userService.thirdBindUser(checkDto);
        }
    }

    /**
     * 新奇账号短信登录校验 已启用
     * @author dzy
     */
    @Deprecated
    private void checkXqSmsLogin(String mobile, Supplier<Boolean> supplier) {

        String errorKey = CacheConstants.SMS_ERR_CNT_KEY + mobile;
        String errorTimeKey = CacheConstants.SMS_ERR_TIME_KEY + mobile;

        Integer maxRetryCount = userPasswordProperties.getMaxRetryCount();
        Integer lockTime = userPasswordProperties.getLockTime();

        // 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
        Integer errorNumber = RedisUtils.getCacheObject(errorKey);
        //密码错误的时间戳
        Long errorTime = RedisUtils.getCacheObject(errorTimeKey);
        Long nowTime = DateUtils.getNowDateTime();
        if (ObjectUtil.isNotNull(errorTime) && ObjectUtil.isNotNull(errorNumber)) {
            //上次密码错误时间距离当前时间间隔
            Integer distanceTime = new Double(Math.floor((nowTime - errorTime) / 1000 / 60)).intValue();
            if (ObjectUtil.isNotNull(errorNumber) && errorNumber >= maxRetryCount && distanceTime < 60) {
                //密码解锁存活时间
                Integer unlockedTime = new Double(Math.floor((RedisUtils.getTimeToLive(errorKey)) / 1000 / 60)).intValue();
                throw new UserException(LoginType.XQSMS.getRetryLimitExceed(), unlockedTime);
            }
        }

        if (supplier.get()) {
            //记录验证码错误的时间戳
            errorTime = nowTime;
            RedisUtils.setCacheObject(errorTimeKey, errorTime, Duration.ofMinutes(60));
            // 是否第一次
            errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
            // 达到规定错误次数 则锁定登录
            if (errorNumber.equals(maxRetryCount)) {
                //设置验证码错误次数
                RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
                //验证码解锁存活时间
                Integer unlockedTime = new Double(Math.floor((RedisUtils.getTimeToLive(errorKey)) / 1000 / 60)).intValue();
                throw new UserException(LoginType.XQSMS.getRetryLimitExceed(), unlockedTime);

            } else {
                //设置验证码错误次数
                RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
                // 未达到规定错误次数
                throw new UserException(MessageUtils.message("sms.code.is.invalid"));
            }
        }
        // 校验成功 清空错误次数
        RedisUtils.deleteObject(errorKey);
        // 校验成功 清空输入次数
        RedisUtils.deleteObject(errorTimeKey);
    }

    /**
     * 校验短信验证码
     * @author dzy
     */
    private boolean validateSmsCode(String mobile, String smsCode, Boolean flag) {
        // TODO 上线需注释掉万能验证码 666666
        if (Objects.equals("666666", smsCode)) {
            return true;
        }
        Integer times = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY_TIMES + mobile);
        if (Objects.isNull(times)) {
            times = 1;
            RedisUtils.setCacheObject(CacheConstants.CAPTCHA_CODE_KEY_TIMES + mobile, 1, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
        } else {
            RedisUtils.setCacheObject(CacheConstants.CAPTCHA_CODE_KEY_TIMES + mobile, times + 1);
        }
        if (times > CacheConstants.CAPTCHA_CODE_KEY_MAX) {
            RedisUtils.deleteObject(CacheConstants.CAPTCHA_CODE_KEY_TIMES + mobile);
            RedisUtils.deleteObject(CacheConstants.CAPTCHA_CODE_KEY + mobile);
            CommonErrors.BAD_REQUEST.check(false, MessageUtils.message("user.jcaptcha.max.valid"));
        }
        String code = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + mobile);
        if (StringUtils.isBlank(code)) {
            CommonErrors.BAD_REQUEST.check(false, MessageUtils.message("user.jcaptcha.is_blank"));
        }
        boolean checkFlag = code.equals(smsCode);
        if (checkFlag && flag) {
            RedisUtils.deleteObject(CacheConstants.CAPTCHA_CODE_KEY + mobile);
        }
        return checkFlag;
    }

    /**
     * 防止密码反向暴力试账号
     * @param username
     */
    private void checkXqULogin(String username) {
        Integer maxRetryCount = userPasswordProperties.getMaxRetryCount();
        if(null == maxRetryCount) {
            return;
        }
        String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
        if(RedisUtils.hasKey(errorKey)) {
            Integer errorNumber = RedisUtils.getCacheObject(errorKey);
            if(errorNumber >= maxRetryCount) {
                throw new ServiceException("用户名或密码错误达到上限");
            }
        }
    }

    /**
     * 新奇账号密码登录错误次数校验
     */
    private void checkXqLogin(String username, Supplier<Boolean> supplier) {
        String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
        String errorTimeKey = CacheConstants.PWD_ERR_TIME_KEY + username;
        String loginFail = Constants.LOGIN_FAIL;
        Integer maxRetryCount = userPasswordProperties.getMaxRetryCount();
        Integer lockTime = userPasswordProperties.getLockTime();

        // 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
        Integer errorNumber = RedisUtils.getCacheObject(errorKey);
        //密码错误的时间戳
        Long errorTime = RedisUtils.getCacheObject(errorTimeKey);
        Long nowTime = DateUtils.getNowDateTime();
        if (ObjectUtil.isNotNull(errorTime) && ObjectUtil.isNotNull(errorNumber)) {
            //上次密码错误时间距离当前时间间隔
            Integer distanceTime = new Double(Math.floor((nowTime - errorTime) / 1000 / 60)).intValue();
            if (ObjectUtil.isNotNull(errorNumber) && errorNumber >= maxRetryCount && distanceTime < 60) {
                //密码解锁存活时间
                Integer unlockedTime = new Double(Math.floor((RedisUtils.getTimeToLive(errorKey)) / 1000 / 60)).intValue();
                throw new UserException(LoginType.XQPASSWORD1.getRetryLimitExceed(), unlockedTime);
            }
        }

        if (supplier.get()) {
            //记录密码错误的时间戳
            errorTime = nowTime;
            RedisUtils.setCacheObject(errorTimeKey, errorTime, Duration.ofMinutes(60));
            // 是否第一次
            errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
            // 达到规定错误次数 则锁定登录
            if (errorNumber.equals(maxRetryCount)) {
                //设置密码错误次数
                RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
                //密码解锁存活时间
                Integer unlockedTime = new Double(Math.floor((RedisUtils.getTimeToLive(errorKey)) / 1000 / 60)).intValue();
                throw new UserException(LoginType.XQPASSWORD3.getRetryLimitExceed(), unlockedTime);

            } else {
                //设置密码错误次数
                RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
                // 未达到规定错误次数
                Integer leftChance = maxRetryCount - errorNumber;
                throw new UserException(LoginType.XQPASSWORD2.getRetryLimitExceed(), leftChance);
            }
        }
        // 登录成功 清空错误次数
        RedisUtils.deleteObject(errorKey);
        //清空登录错误时间
        RedisUtils.deleteObject(errorTimeKey);

    }

    public R<Map<String, Object>> registerLastStep(RegisterLastStepDTO lastStep) {
        UserResultDTO userInfo = userService.registerLastStep(lastStep);
        Map<String, Object> rspMap = new HashMap<>();
        rspMap.put("access_token", StpUtil.getTokenValue());
        rspMap.put("user", userInfo);
        return R.ok(100, "操作成功", rspMap);
    }

    public R checkSmsCode(SmsLoginBody form) {
        CommonErrors.NOT_FOUND.check(validateSmsCode(form.getMobile(), form.getSmsCode(), false), "验证码错误");
        return R.ok("验证码输入正确");
    }

    public R<Map<String, Object>> flushUserToken(FlushTokenBody body) {
        // 通过手机号查找用户
        UserResultDTO userInfo = userService.getXqUserInfoByPhone(body.getMobile());
        if (userInfo == null) {
            return R.fail(RegisterReturnEnum.USER_NOT_REGISTER.getCode(), RegisterReturnEnum.USER_NOT_REGISTER.getDescription());
        }
        Map<String, Object> map = new HashMap<String, Object>();
        LoginHelper.loginXqByDevice(userInfo, DeviceType.getDeviceType(body.getDeviceType()));

        String token = StpUtil.getTokenValue();
        map.put("user", userInfo);
        map.put("access_token", token);
        map.put("platForm", body.getDeviceType());
        return R.ok(map);
    }

    /**
     * 注册并登录
     * @author dzy
     */
    @Transactional(rollbackFor = Exception.class)
    public R<Map<String, Object>> registerAndSignIn(UserRegisterDTO register) {

        CommonErrors.BAD_REQUEST.check(Objects.nonNull(register.getNickName()), "请输入昵称");
        CommonErrors.BAD_REQUEST.check(register.getNickName().length() <= 15, "昵称不能超过15个字符");
        CommonErrors.BAD_REQUEST.check(Objects.nonNull(register.getPassword()), "请输入密码");
        CommonErrors.BAD_REQUEST.check(register.getPassword().matches(UserConstants.PASSWORD_PATTERN), "密码必须为8到18位的字母和数字组合");
        CommonErrors.BAD_REQUEST.check(Objects.nonNull(register.getMobile()), "请输入手机号码");
        CommonErrors.BAD_REQUEST.check(register.getMobile().matches(UserConstants.MOBILE_PHONE_NUMBER_PATTERN), "手机号格式不正确");
        CommonErrors.BAD_REQUEST.check(Objects.nonNull(register.getSchoolName()), "请输入学校");
        CommonErrors.BAD_REQUEST.check(register.getSchoolName().length() <= 50, "学校名称不能超过50个字符");
        CommonErrors.BAD_REQUEST.check(Objects.nonNull(register.getUserRole()), MessageUtils.message("user.role.is.not.blank"));
        CommonErrors.BAD_REQUEST.check(Objects.nonNull(register.getPlatform()), "注册平台来源不能为空");

        //校验短信验证码
        checkXqSmsLogin(register.getMobile(), () -> !validateSmsCode(register.getMobile(), register.getSmsCode(), true));

        // 通过手机号查找用户
        UserResultDTO userInfo = userService.selectLoginMsgByMobile(register.getMobile());
        if (userInfo != null) {
            return R.fail("手机号已注册");
        }

        Map<String, Object> map = new HashMap<>();
        UserResultDTO ueUser = userService.registerXqAppUser(register);

        LoginHelper.loginXqByDevice(ueUser, DeviceType.getDeviceType(register.getPlatform()));

        String token = StpUtil.getTokenValue();
        map.put("user", ueUser);
        map.put("access_token", token);
        map.put("platForm", register.getPlatform());

        if (StringUtils.isEmpty(ueUser.getSchoolName())) {
            return R.ok(RegisterReturnEnum.SUPPLY_INFO.getCode(), RegisterReturnEnum.SUPPLY_INFO.getDescription(), map);
        }

        //新奇提醒机器人 -- 首次进入
        CompletableFuture.runAsync(() -> {
            remindContentClient.insert(new ChatRemindContentCreateDTO(ueUser.getId(), ChatRemindTypeEnum.FIRST_ENTRY, ChatRemindConstants.REMIND_FIRST_ENTRY_TEMPLATE));
        });

        // 同步示范课程
        Spring.committed(() -> {
            if (Objects.equals(RegisterRoleEnum.TEACHER.getCode(), register.getUserRole())) {
                remoteCourseClient.initDemonstrate(ueUser.getId());
            }
        });
        // 异步更新融云用户信息
        Spring.committedAsync(()-> {
            rongyunClient.updateUser(ueUser.getId().toString(), ueUser.getNickname(), ueUser.getAvatar());
        });
        // 注册成功记录一次登录
        this.loginUserLog(UserUseTypeEnum.LOGIN, StpUtil.getLoginDevice().toLowerCase());
        return R.ok(map);
    }

    /**
     * 忘记密码第一步
     * @param check
     * @author: dzy
     */
    public boolean justCheck(SmsCheckBody check) {
        CommonErrors.NOT_FOUND.check(validateSmsCode(check.getMobile(), check.getSmsCode(), false), "验证码错误");
        return true;
    }

    /**
     * 重置密码
     * @author dzy
     */
    public R<Void> resetpwd(PasswordBody passwordBody) {

        String mobile = passwordBody.getMobile();
        String smsCode = passwordBody.getSmsCode();

        CommonErrors.BAD_REQUEST.check(Objects.nonNull(passwordBody.getPassword()), "请输入密码");
        if (!passwordBody.getPassword().matches(UserConstants.PASSWORD_PATTERN)) {
            return R.fail("密码必须为8到18位的字母和数字组合");
        }

        //校验短信验证码
        checkXqSmsLogin(mobile, () -> !validateSmsCode(mobile, smsCode, false));
        // 通过手机号查找用户
        UserResultDTO userInfo = userService.getXqUserInfoByPhone(mobile);
        if (ObjectUtil.isNull(userInfo)) {
            throw new ServiceException(MessageUtils.message("user.unregistered"));
        }
        PasswordResetDTO user = new PasswordResetDTO();
        user.setId(userInfo.getId());
        user.setSalt(Md5HashUtil.randomSalt());
        user.setPassword(Md5HashUtil.encryptPwd(user.getSalt(), passwordBody.getPassword()));
        userService.resetPassword(user);
        // 所有端退出登录状态
        StpUtil.logout(userInfo.getId());
        return R.ok("密码重置成功");
    }

    /**
     * 刷新Token时间
     * @author dzy
     */
    public void freshTokenTime() {
        try {
            UserResultDTO user = userService.selectById(LoginHelper.getUserId());
            LoginHelper.updateTokenTimer(user);
        } catch (NotLoginException ignored) {
        }
    }

    /**
     * 获取扫码用户信息
     * @author dzy
     */
    public R<Void> createMessageInfo(String key) {

        // 判断二维码是否过期
        String singStr = RedisUtils.getCacheObject(CacheConstants.SOCKET_LOGIN_SCAN_KEY + key);
        CommonErrors.BAD_REQUEST.check(Objects.nonNull(singStr), "二维码已过期");

        // 获取用户登录信息
        Account user = LoginHelper.getXqLoginUser();
        CommonErrors.BAD_REQUEST.check(Objects.nonNull(user), "未获取到用户信息");
        UserLoginResultDTO userInfo = userService.selectLoginMsgById(user.getId());
        CommonErrors.BAD_REQUEST.check(Objects.nonNull(userInfo), "未获取到用户信息");

        // 发送消息
        MessageInfo sendData = new MessageInfo();
        sendData.setSourceClientId("xinqi-sys");
        sendData.setTargetClientId(key);
        sendData.setMsgType("xqsocket");
        Map<String, Object> map = new HashMap<>();
        map.put("user", userInfo);
        // 生成token
        LoginHelper.loginXqByDevice(user, DeviceType.PC);
        String token = StpUtil.getTokenValue();
        map.put("access_token", token);
        sendData.setMsgContent(JSON.stringify(map));
        RedisUtils.publish(CacheConstants.SOCKET_LOGIN_SCAN_TOPIC, sendData);
        return R.ok("扫码成功");
    }

    /**
     * 退出登录
     */
    public void logout() {
        try {
            String deviceType = StpUtil.getLoginDevice().toLowerCase();
            StpUtil.logout();
            this.loginUserLog(UserUseTypeEnum.LOGIN_OUT, deviceType);
        } catch (NotLoginException ignored) {
        }
    }

    /**
     * 登录登出用户日志记录
     */
    private void loginUserLog(UserUseTypeEnum typeEnum, String deviceType) {
        UserUseCreateDTO createDTO = new UserUseCreateDTO();
        createDTO.setType(typeEnum.getCode());
        if (Objects.equals("pc", deviceType)) {
            createDTO.setStyle(UserUseStyleEnum.PC.getCode());
        } else {
            createDTO.setStyle(UserUseStyleEnum.APP.getCode());
        }
        userUseLogService.addUserUseLog(createDTO);
    }

}
